﻿using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace Sigil.Impl
{
	internal class TypeOnStack : IComparable
	{
		private class TypeCache
		{
			private static ConcurrentDictionary<Type, TypeOnStack> Cache = new ConcurrentDictionary<Type, TypeOnStack>();

			public static TypeOnStack Get(Type t)
			{
				//lock (Cache)
				//{
				TypeOnStack ret;
				if (!Cache.TryGetValue(t, out ret))
				{
					ret = new TypeOnStack { Type = t };

					Cache[t] = ret;
				}

				return ret;
				//}
			}
		}

		public static Boolean operator ==(TypeOnStack a, TypeOnStack b)
		{
			if (Object.ReferenceEquals(a, b)) return true;
			if (Object.ReferenceEquals(a, null)) return false;
			if (Object.ReferenceEquals(b, null)) return false;

			// There's not exact map of NativeInt in .NET; but once it's on the stack IntPtr can be manipulated similarly
			if (a.Type == typeof(NativeIntType) && b.Type == typeof(IntPtr) || b.Type == typeof(NativeIntType) && a.Type == typeof(IntPtr))
			{
				return a.IsPointer == b.IsPointer && a.IsReference == b.IsReference;
			}

			return
					a.Type == b.Type &&
					a.IsPointer == b.IsPointer &&
					a.IsReference == b.IsReference;
		}

		public static Boolean operator !=(TypeOnStack a, TypeOnStack b)
		{
			return !(a == b);
		}

		private static readonly ConcurrentDictionary<Tuple<CallingConventions, Type, Type, Type[]>, TypeOnStack> KnownFunctionPointerCache = new ConcurrentDictionary<Tuple<CallingConventions, Type, Type, Type[]>, TypeOnStack>();

		public Type Type { get; private set; }

		public Boolean IsReference { get { return Type.IsByRef; } }

		public Boolean IsPointer { get { return Type.IsPointer; } }

		public Boolean IsArray { get { return Type.IsArray; } }

		public Boolean HasAttachedMethodInfo { get; private set; }

		public CallingConventions CallingConvention { get; private set; }

		public Type InstanceType { get; private set; }

		public Type ReturnType { get; private set; }

		public Type[] ParameterTypes { get; private set; }

		public Boolean IsMarkable { get { return UsedBy != null; } }

		internal HashSet<Tuple<InstructionAndTransitions, Int32>> UsedBy { get; set; }

		/// <summary>
		/// Call to indicate that something on the stack was used
		/// as the #{index}'d (starting at 0) parameter to the {code}
		/// opcode.
		/// </summary>
		public void Mark(InstructionAndTransitions instr, Int32 index)
		{
			UsedBy.Add(Tuple.Create(instr, index));
		}

		/// <summary>
		/// Returns the # of times this value was used as the given #{index}'d parameter to the {code} instruction.
		/// </summary>
		public Int32 CountMarks(OpCode code, Int32 ix)
		{
			return UsedBy.Count(c => c.Item1.Instruction.Equals(code) && c.Item2 == ix);
		}

		/// <summary>
		/// Returns the total number of times this value was marked.
		/// </summary>
		public Int32 CountMarks()
		{
			return UsedBy.Count;
		}

		public static TypeOnStack Get<T>()
		{
			return Get(typeof(T));
		}

		public static TypeOnStack Get(Type type)
		{
			if (type.ContainsGenericParameters)
			{
				throw new InvalidOperationException("Sigil does not currently support generic types; found " + type);
			}

			if (type == typeof(Char)) type = typeof(UInt16);

			var ret = TypeCache.Get(type);

			return
					new TypeOnStack
					{
						CallingConvention = ret.CallingConvention,
						HasAttachedMethodInfo = ret.HasAttachedMethodInfo,
						InstanceType = ret.InstanceType,
						ParameterTypes = ret.ParameterTypes,
						ReturnType = ret.ReturnType,
						Type = ret.Type,
						UsedBy = new HashSet<Tuple<InstructionAndTransitions, Int32>>()
					};
		}

		public static TypeOnStack GetKnownFunctionPointer(CallingConventions conv, Type instanceType, Type returnType, Type[] parameterTypes)
		{
			var key = Tuple.Create(conv, instanceType, returnType, parameterTypes);

			TypeOnStack ret;

			//lock (KnownFunctionPointerCache)
			//{
			if (!KnownFunctionPointerCache.TryGetValue(key, out ret))
			{
				ret = new TypeOnStack
						{
							HasAttachedMethodInfo = true,
							Type = typeof(NativeIntType),
							CallingConvention = conv,
							InstanceType = instanceType,
							ReturnType = returnType,
							ParameterTypes = parameterTypes,
							UsedBy = new HashSet<Tuple<InstructionAndTransitions, Int32>>()
						};

				KnownFunctionPointerCache[key] = ret;
			}
			//}

			return ret;
		}

		public override String ToString()
		{
			var ret = Type.FullName;

			if (Type == typeof(NativeIntType)) ret = "native int";
			if (Type == typeof(NullType)) ret = "null";
			if (Type == typeof(Int32)) ret = "int";
			if (Type == typeof(Int64)) ret = "long";
			if (Type == typeof(Single)) ret = "float";
			if (Type == typeof(Double)) ret = "double";
			if (Type == typeof(AnyPointerType)) ret = "pointer";
			if (Type == typeof(AnyByRefType)) ret = "by ref";
			if (Type == typeof(OnlyObjectType)) ret = typeof(object).ToString();

			return ret;
		}

		public override Boolean Equals(Object obj)
		{
			var other = obj as TypeOnStack;
			return this == other;
		}

		public override Int32 GetHashCode()
		{
			return
					(Int32)(
							Type.GetHashCode() ^
							(IsPointer ? 0x0000FFFF : 0) ^
							(IsReference ? 0xFFFF0000 : 0)
					);
		}

		public Boolean IsAssignableFrom(TypeOnStack other)
		{
			return ExtensionMethods.IsAssignableFrom(this, other);
		}

		public Int32 CompareTo(Object obj)
		{
			if (Object.ReferenceEquals(this, obj)) return 0;
			if (Object.ReferenceEquals(obj, null)) return 1;

			return this.ToString().CompareTo(obj.ToString());
		}
	}
}